// tge.rs
// copyright zipxing@hotmail.com 2022~2023

//! Terminal支持终端和SDL两种模式绘制渲染
//! 核心功能是把buffer的内容渲染到屏幕上
//!
//! 终端模式采用crossterm模块，并内置了双缓冲处理
//!
//! SDL模式采用rustsdl2模块，为支持sprite重叠透明
//! buffer在SDL模式下记录了每个cell每帧更新的符号
//! 颜色，渲染时按顺序依次绘制在render_texture
//! 中，然后再把render_texture绘制展示在canva里.
//! 为了实现更强大的功能，SDL模式下支持一组可以
//! 按照像素设置位置的特殊sprites：sdl_pixel_sprites
//! 这些sprites还是按照cell方式管理内容，但在渲染
//! 时，可以按照像素位置进行渲染，也可以围绕中心点
//! 进行旋转, 参看flush方法。还可以参考games/tower
//! 这个塔防游戏示例了大量pixel_sprite的用法.
//!
//! 还可以调用set_sdl_texture_data动态修改texture
//! 点阵数据，赋予更大的扩展性
//!
use crate::{
    context::Context,
    LOGO_FRAME,
    render::{
        buffer::Buffer,
        sprite::{Sprite, Sprites, Widget},
    },
    util::Rect,
};
use std::io;

pub struct Term {
    buffers: [Buffer; 2],
    current: usize,
    pub sdl_pixel_sprites: Sprites,
}

/// Represents a consistent terminal interface for rendering.
pub struct Frame<'a> {
    terminal: &'a mut Term,
    cursor_position: Option<(u16, u16)>,
}

impl<'a> Frame<'a> {
    pub fn render_widget<D: Widget>(&mut self, w: &D) {
        w.render(self.terminal.current_buffer_mut());
    }

    pub fn set_cursor(&mut self, x: u16, y: u16) {
        self.cursor_position = Some((x, y));
    }
}

#[allow(unused)]
impl Term {
    #[allow(unused_mut)]
    pub fn new() -> Self {
        let (width, height) = (180, 80);

        let size = Rect::new(0, 0, width, height);
        let sc = Sprites::new("pixel");
        Term {
            buffers: [Buffer::empty(size), Buffer::empty(size)],
            current: 0,
            sdl_pixel_sprites: sc,
        }
    }

    pub fn init(&mut self, ctx: &mut Context) {
        let size = ctx.adapter.size();
        self.buffers[0].resize(size);
        self.buffers[1].resize(size);
    }

    pub fn get_frame(&mut self) -> Frame {
        Frame {
            terminal: self,
            cursor_position: None,
        }
    }

    pub fn current_buffer_mut(&mut self) -> &mut Buffer {
        &mut self.buffers[self.current]
    }

    pub fn add_pixel_sprite(&mut self, sp: Sprite, tag: &str) {
        self.sdl_pixel_sprites.add_by_tag(sp, tag);
    }

    pub fn get_pixel_sprite(&mut self, tag: &str) -> &mut Sprite {
        self.sdl_pixel_sprites.get_by_tag(tag)
    }

    pub fn reset(&mut self, ctx: &mut Context) {
        ctx.adapter.reset();
    }

    pub fn draw<F>(&mut self, ctx: &mut Context, f: F) -> io::Result<()>
    where
        F: FnOnce(&mut Frame),
    {
        let mut frame = self.get_frame();
        f(&mut frame);

        let cb = &self.buffers[self.current];
        let pb = &self.buffers[1 - self.current];

        ctx.adapter
            .render_buffer(cb, pb, &mut self.sdl_pixel_sprites, ctx.stage)
            .unwrap();
        ctx.adapter.hide_cursor().unwrap();

        // Swap buffers
        if ctx.stage > LOGO_FRAME {
            self.buffers[1 - self.current].reset();
            self.current = 1 - self.current;
        }

        Ok(())
    }
}
